﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using OmronLib.Base;

namespace OmronLib
{
    public class CIP : FinsCommand
    {
        private byte _slot = 0x00;
        private byte[] _session_handle;
        private byte[] _attachInfo;
        private Socket _socket;

        public CIP(string attachInfo, byte slot = 0x00)
        {
            _attachInfo = Encoding.UTF8.GetBytes(attachInfo).Take(8).ToArray();
            _slot = slot;
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }


        public override void Open(string Ip, int Port, int timeout = 3000)
        {
            _socket.ReceiveTimeout = timeout;
            _socket.Connect(Ip, Port);

            byte[] req_bytes = new byte[]
           {
                0x65,0x00,
                0x04,0x00,
                0x00,0x00,0x00,0x00,    // session_id
                0x00,0x00,0x00,0x00,    // status code
                _attachInfo[0],_attachInfo[1],_attachInfo[2],_attachInfo[3],_attachInfo[4],_attachInfo[5],_attachInfo[6],_attachInfo[7],
                0x00,0x00,0x00,0x00,

                0x01,0x00,      // 协议版本号 固定 0x01
                0x00,0x00       // 选项
           };

            _socket.Send(req_bytes);
            byte[] resp_head = new byte[4];
            int count = _socket.Receive(resp_head, 0, 4, SocketFlags.None);
            if (count == 0)
                throw new Exception("注册Session会话失败");
            byte[] len_byte = resp_head.Skip(2).ToArray();  // the default value of response is little endian;
            if (!BitConverter.IsLittleEndian)
                Array.Reverse(len_byte);
            ushort resp_len = BitConverter.ToUInt16(len_byte);

            byte[] resp_body = new byte[resp_len];
            _socket.Receive(resp_body, 0, resp_len, SocketFlags.None);
            byte[] status_bytes = resp_body.Skip(4).Take(4).ToArray();

            if (CipErrors.HeaderErrors.ContainsKey(status_bytes[3]))
                throw new Exception(CipErrors.HeaderErrors[status_bytes[3]]);
            _session_handle = resp_body.Take(4).ToArray();
        }

        public override void Close()
        {
            if (_socket == null || !_socket.Connected) return;
            _socket.Disconnect(true);
            _socket.Close();
            _socket.Dispose();
        }

        public byte[] Read(string tag)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x6F,0x00,
                0x00,0x00,  // --------------------------------- 2,3
                _session_handle[0],_session_handle[1],_session_handle[2],_session_handle[3],
                0x00,0x00,0x00,0x00,    // status code
                _attachInfo[0],_attachInfo[1],_attachInfo[2],_attachInfo[3],_attachInfo[4],_attachInfo[5],_attachInfo[6],_attachInfo[7],
                0x00,0x00,0x00,0x00,   

                0x00,0x00,0x00,0x00,    // len = 28
                0x01,0x00,
                0x02,0x00,  // address item 与 data item 个数

                0x00,0x00,
                0x00,0x00,

                0xB2,0x00,
                0x00,0x00,  // --------------------------------- 38,39

                0x52,
                0x02,
                0x20,0x06,0x24,0x01,
                0x0A,0x00,// 默认超时时间
                0X00,0X00,   // --------------------------------- 48,49
                0x4C,
                0x00, // --------------------- 51
            };

            List<byte> tag_list = Encoding.ASCII.GetBytes(tag).ToList();
            byte tag_len = (byte)(tag_list.Count % 256);
            if(tag_list.Count % 2 == 1)
            {
                tag_list.Add(0x00);
            }
            tag_list.Insert(0, 0x91);
            tag_list.Insert(1, tag_len);
            byte tag_word_len = (byte)(tag_list.Count / 2);

            int sp_len = tag_list.Count + 4;
            int item_len = sp_len + 14;
            int total_len = item_len + 16;

            req_bytes[2] = (byte)(total_len % 256);
            req_bytes[3] = (byte)(total_len / 256);

            req_bytes[38] = (byte)(item_len % 256);
            req_bytes[39] = (byte)(item_len / 256);

            req_bytes[48] = (byte)(sp_len % 256);
            req_bytes[49] = (byte)(sp_len / 256);
            req_bytes[51] = tag_word_len;

            req_bytes.Add(0x01);
            req_bytes.Add(0x00);
            req_bytes.Add(0x01);
            req_bytes.Add(0x00);
            req_bytes.Add(0x01);
            req_bytes.Add(_slot);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);

            // 类型判断
            string type_str = resp_bytes[44].ToString("X2");
            int data_len = -1;
            if(CipTypes.TypeLength.ContainsKey(type_str))
            {
                data_len = CipTypes.TypeLength[type_str];
            }

            if(data_len == 0)
            {
                data_len = resp_bytes[46] + resp_bytes[47] * 256;
                return resp_bytes.Skip(48).Take(data_len).ToArray();
            }
            data_len /= 8;
            byte[] res_bytes = resp_bytes.Skip(46).Take(data_len).ToArray();
            Array.Reverse(res_bytes);
            return res_bytes;
        }

        public void Write(string tag, CipDataType dataType, byte[] bytes)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x6F,0x00,
                0x00,0x00,  // --------------------------------- 2,3
                _session_handle[0],_session_handle[1],_session_handle[2],_session_handle[3],
                 0x00,0x00,0x00,0x00,    // status code
                _attachInfo[0],_attachInfo[1],_attachInfo[2],_attachInfo[3],_attachInfo[4],_attachInfo[5],_attachInfo[6],_attachInfo[7],
                0x00,0x00,0x00,0x00,

                0x00,0x00,0x00,0x00,    // len = 28
                0x01,0x00,
                0x02,0x00,  // address item 与 data item 个数

                0x00,0x00,
                0x00,0x00,

                0xB2,0x00,
                0x00,0x00,  // --------------------------------- 38,39

                0x52,
                0x02,
                0x20,0x06,0x24,0x01,
                0x0A,0x00,   // 默认超时时间
                0X00,0X00,   // --------------------------------- 48,49
                0x4D,
                0x00, // --------------------- 51
            };

            List<byte> tag_list = Encoding.ASCII.GetBytes(tag).ToList();
            byte tag_len = (byte)(tag_list.Count % 256);
            if (tag_list.Count % 2 == 1)
            {
                tag_list.Add(0x00);
            }
            tag_list.Insert(0, 0x91);
            tag_list.Insert(1, tag_len);
            byte tag_word_len = (byte)(tag_list.Count / 2);

            tag_list.Add((byte)dataType);
            tag_list.Add(0x00);
            tag_list.Add(0x01);
            tag_list.Add(0x00);

            if (dataType != CipDataType.STRING) 
            {
                Array.Reverse(bytes);
                tag_list.AddRange(bytes);
                if (bytes.Length % 2 == 1)
                {
                   tag_list.Add(0x00);
                }
                    
            }
            else
            {
                // 字符串类型特殊处理
                byte[] len_bytes = BitConverter.GetBytes((ushort)bytes.Length);
                if(!BitConverter.IsLittleEndian) 
                    Array.Reverse(len_bytes);
                tag_list.AddRange(len_bytes);
                tag_list.AddRange(bytes);
            }

            int sp_len = tag_list.Count + 4;
            int item_len = sp_len + 14;
            int total_len = item_len + 16;

            req_bytes[2] = (byte)(total_len % 256);
            req_bytes[3] = (byte)(total_len / 256);

            req_bytes[38] = (byte)(item_len % 256);
            req_bytes[39] = (byte)(item_len / 256);

            req_bytes[48] = (byte)(sp_len % 256);
            req_bytes[49] = (byte)(sp_len / 256);
            req_bytes[51] = tag_word_len;

            req_bytes.AddRange(tag_list);
            req_bytes.Add(0x01);
            req_bytes.Add(0x00);
            req_bytes.Add(0x01);
            req_bytes.Add(_slot);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);
        }

        public void MultiReadCJ(params CipParameter[] parameters)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x6F,0x00,
                0x00,0x00,  // --------------------------------- 2,3
                _session_handle[0],_session_handle[1],_session_handle[2],_session_handle[3],
                0x00,0x00,0x00,0x00,    // status code
                _attachInfo[0],_attachInfo[1],_attachInfo[2],_attachInfo[3],_attachInfo[4],_attachInfo[5],_attachInfo[6],_attachInfo[7],
                0x00,0x00,0x00,0x00,

                0x00,0x00,0x00,0x00,    // len = 28
                0x01,0x00,
                0x02,0x00,  // address item 与 data item 个数

                0x00,0x00,
                0x00,0x00,

                0xB2,0x00,
                0x00,0x00,  // --------------------------------- 38,39

                0x52,
                0x02,
                0x20,0x06,0x24,0x01,
                0x0A,0x00,   // 默认超时时间
                0X00,0X00,   // --------------------------------- 48,49
                0x4C,
                0x00, // --------------------- 51
            };


        }

        public void MultiReadNJ_NX(params CipParameter[] parameters)
        {
            List<byte> req_bytes = new List<byte>()
            {
                0x6F,0x00,
                0x00,0x00,  // --------------------------------- 2,3
                _session_handle[0],_session_handle[1],_session_handle[2],_session_handle[3],
                 0x00,0x00,0x00,0x00,    // status code
                _attachInfo[0],_attachInfo[1],_attachInfo[2],_attachInfo[3],_attachInfo[4],_attachInfo[5],_attachInfo[6],_attachInfo[7],
                0x00,0x00,0x00,0x00,

                0x00,0x00,0x00,0x00,    // len = 28
                0x01,0x00,
                0x02,0x00,  // address item 与 data item 个数

                0x00,0x00,
                0x00,0x00,

                0xB2,0x00,
                0x1E,0x00,  // --------------------------------- 38,39

                0x52,
                0x02,
                0x20,0x06,0x24,0x01,
                0x0A,0x00,// 默认超时时间
                0X10,0X00,   // --------------------------------- 48,49
                0x0A,
                0x02,
                0x20,0x06,0x24,0x01
            };
            // Request Data Start
            req_bytes.Add((byte)(parameters.Length % 256));
            req_bytes.Add((byte)(parameters.Length / 256 % 256));

            int offset = parameters.Length * 2 + 2;
            List<byte> tag_bytes = new List<byte>();
            foreach (var tag in parameters)
            {
                req_bytes.Add((byte)(offset % 256));
                req_bytes.Add((byte)(offset / 256 % 256));

                tag_bytes.Add(0x4C);

                byte[] data_bytes = Encoding.UTF8.GetBytes(tag.Tag);
                tag_bytes.Add((byte)Math.Ceiling((data_bytes.Length + 2) * 1.0 / 2));
                tag_bytes.Add(0x91);
                tag_bytes.Add((byte)data_bytes.Length);
                tag_bytes.AddRange(data_bytes);
                if (data_bytes.Length % 2 == 1)
                    tag_bytes.Add(0x00);
                tag_bytes.Add(0x01);
                tag_bytes.Add(0x00);

                offset += tag_bytes.Count;
            }
            
        
            req_bytes.AddRange(tag_bytes.ToArray());
            req_bytes.Add(0x01);
            req_bytes.Add(0x00);
            req_bytes.Add(0x01);
            req_bytes.Add(_slot);

            byte[] resp_bytes = this.SendAndReceive(req_bytes.ToArray());
            this.CheckResponse(resp_bytes);

            int offset_pos = 46;
            int tmp = 0;
            foreach (var tag in parameters)
            {
                byte[] offset_bytes = resp_bytes.Skip(offset_pos + tmp).Take(2).ToArray();
                int item_offset = offset_bytes[0] + offset_bytes[1] * 256 + offset_pos;
                string item_status = string.Join("", resp_bytes.Skip(item_offset).Take(2).Select(b => b.ToString("X2")));
                if (!"0000".Equals(item_status))
                {
                    if (CipErrors.RespErrors.ContainsKey(item_status))
                        throw new Exception(CipErrors.RespErrors[item_status]);
                    else
                        throw new Exception($"未知错误,item_status: {item_status}");
                }
                item_offset += 2;
                string data_type = resp_bytes[item_offset].ToString("X2");
                int data_len = -1;
                if(CipTypes.TypeLength.ContainsKey(data_type))
                    data_len = CipTypes.TypeLength[data_type];
                if (data_len == -1)
                    tag.ErrorMsg = "未知数据类型";

                item_offset += 2;
                if(data_len == 0)
                {
                    byte[] data_len_bytes = resp_bytes.Skip(item_offset).Take(2).ToArray();
                    int len = data_len_bytes[0] + data_len_bytes[1] * 256;
                    item_offset += 2;
                    tag.Data = resp_bytes.Skip(item_offset).Take(len).ToArray();
                }
                else
                {
                    data_len /= 8;
                    tag.Data = resp_bytes.Skip(item_offset).Take(data_len).ToArray();
                }

                tmp += 2;
            }
        }

        private byte[] SendAndReceive(byte[] bytes)
        {
            _socket.Send(bytes);
            byte[] resp_head = new byte[24];
            int count = _socket.Receive(resp_head, 0, 24, SocketFlags.None);
            if (count == 0)
                throw new Exception("响应结果异常");
            byte[] len_bytes = resp_head.Skip(2).Take(2).ToArray(); // default is little Endian
            if (!BitConverter.IsLittleEndian)
                Array.Reverse(len_bytes);
            ushort resp_len = BitConverter.ToUInt16(len_bytes);
            byte[] resp_body = new byte[resp_len];
            _socket.Receive(resp_body, 0, resp_len, SocketFlags.None);
            List<byte> resp_bytes = new List<byte>();
            resp_bytes.AddRange(resp_head);
            resp_bytes.AddRange(resp_body);
            return resp_bytes.ToArray();
        }

        protected override void CheckResponse(byte[] resp_body)
        {
            byte[] status_bytes = resp_body.Skip(8).Take(4).ToArray();

            byte head_code = status_bytes[3];
            if (head_code != 0x00)
            {
                if (CipErrors.HeaderErrors.ContainsKey(head_code))
                    throw new Exception(CipErrors.HeaderErrors[head_code]);
                else
                    throw new Exception($"未知异常: {head_code}");
            }

            string status_code = resp_body[43].ToString("X2") + resp_body[42].ToString("X2");
            if (!"0000".Equals(status_code))
            {
                if (CipErrors.RespErrors.ContainsKey(status_code)) 
                    throw new Exception(CipErrors.RespErrors[status_code]);
                else
                    throw new Exception($"未知异常: {status_code}");
            }
        }
    }
}
